/* CEClassEditor.m				 
 *
 * This object controls the data of a beaker (molecules, cameras, groups etc.)
 * It is the main document of BeakerBoy and controls everything from loading to
 * setting up the browser which does most of the other work.
 *
 * For interface-info see the header file. The comments in this file mostly
 * cover only the real implementation details.
 *
 * Written by: 		Thomas Engel
 * Created:    		23.10.1993 (Copyleft)
 * Last modified: 	12.11.1994
 */

#define CURRENT_VERSION 1

#import <CEClassEditor.h>
#import <CEClassEditor+FileHandling.h>
#import <CEMethod.h>
#import <CEBrowserCell.h>
#import <CECellMatrix.h>
#import <Text_MiscExtensions.h>
#import <MiscStringArray_List.h>
#import <MiscEmacsText.h>

#import <misckit/MiscString.h>
#import <misckit/MiscStringArray.h>
#import <misckit/MiscSortedList.h>

@implementation CEClassEditor


+ initialize
{
	if ( self == [CEClassEditor class] )
		[CEClassEditor setVersion:CURRENT_VERSION];

	return self;
}

- init
{
	return [self initFromFile:""];
}

- initFromFile:(const char *)fileName
{	
	id	basename;
	id	dummyFile;
	
	id	cellPrototype;
		
	id	headerSearchPathes;
	id	sourceSearchPathes;
	id	docuSearchPathes;
	
	int	index;						// To adjust the popUps initValue !
	id	aMatrix;					// Dito;
	
	self = [super init];
	if( !self ) return self;

	// OK. We really are an object...here we go with our init.
	// Lets load the NIB. It has set our beaker to be its window-delegate.
	// Without a NIB there will be no beaker object !

	if( [NXApp loadNibSection:"ClassWindow.nib" owner:self] == nil )
	{
		NXRunAlertPanel( NULL, "Couldn't load ClassWindow.nib", 
							   "OK", NULL, NULL );
		return nil;
	}
	// Lets setup the names.
	// We need to track all those areas where we update the text to find the
	// places where the user makes changes to the text!
	
	headerSearchPathes = [[[NXApp delegate] preferences]
							headerSearchPaths];
	sourceSearchPathes = [[[NXApp delegate] preferences]
							sourceSearchPaths];
	docuSearchPathes = [[[NXApp delegate] preferences]
							documentationSearchPaths];

	_docuPath = [MiscString new];
	_headerPath = [MiscString new];
	_sourcePath = [MiscString new];
	
	// On we go...

	dummyFile = [[MiscString new] setPath:fileName];
	
	basename = [dummyFile fileBasename];
	filename = [dummyFile pathName];
	[filename addChar:[MiscString pathSeparator]];
	[filename concatenate:basename];
	[basename free];

	// Get the pasteboard and ensure that nobody will see the texthandling we
	// do now.

	_tempPb = [Pasteboard newName:"CETempPb"];	
	[self _setIsUpdatingTextViews:YES];

	// Now we will always refer to the file WITHOUT any extention.
	// If we are lucky we will be able to read it all..m, .h and .rtf
	// If we were able to read the file...make the view editable.
	
	[dummyFile takeStringValue:filename];
	[dummyFile cat:".h"];
	[self _try:dummyFile withAlternatives:headerSearchPathes
			forText:headerFile andGetStyle:&headerStyle andSetPath:_headerPath];
	
	// Well we really need a header file... it is crucial !

	if( headerStyle == CE_FILETYPE_NONE )
	{
		basename = [dummyFile fileBasename];
		NXRunAlertPanel( NULL, "Couldn't load %s.h. Not in any searchpath.", 
							   "Ooops", NULL, NULL, 
						[basename stringValue] );
		[basename free];
		[dummyFile free];
		[[NXApp delegate] addToReleasePool:self];
		return nil;
	}

	// The next part is loading the source code for that class.
	// This can be either ASCII or RTF !
	
	[dummyFile takeStringValue:filename];
	[dummyFile cat:".m"];
	[self _try:dummyFile withAlternatives:sourceSearchPathes
			forText:sourceFile andGetStyle:&sourceStyle andSetPath:_sourcePath];
	
	// Now the documentation. if there is no RTF docu we should ty to find a
	// RTFD docu too !
	
	[dummyFile takeStringValue:filename];
	[dummyFile cat:".rtf"];
	[self _try:dummyFile withAlternatives:docuSearchPathes
			forText:docuFile andGetStyle:&docuStyle andSetPath:_docuPath];
	
	[dummyFile free];
	
	// Its time to check all the methods we can find.
	// NOTE: If there are No methods we will quit right here !
	
	methodList = [List new];
	if( ![self reparseMethods:self] ) return nil;
	
	// If desired we should check to documentation first...maybe we have to add
	// something before we start. But the methods have to be know !!
	
	// if( [preferences should AutoCheckDocu] )
	
	// [self checkDocumentation:self];	

	// Now there are some window settings to be done plus adjusting the
	// browser.
	
	// First lets connect the popUps. This has to be done before the browsers
	// try to get their info.
	// Be sure we have the right initial selection inside the proper matrix !
	
	[[modePopup target] setTarget:self];
	[[modePopup target] setAction:@selector(switchToNewDisplayMode:)];
	index = [[modePopup target] indexOfItem:[modePopup title]];
	
	if( index > -1 )
	{	
		aMatrix = [[modePopup target] itemList];
		
	 	if( ![aMatrix selectedCell] )
			[aMatrix selectCell:[[aMatrix cellList] objectAt:index]];
	}

	// Lets do the same for the other popup..
	
    [[secondaryModePopup target] setTarget:self];
	[[secondaryModePopup target] setAction:@selector(switchToNewDisplayMode:)];
	index = [[secondaryModePopup target] indexOfItem:[modePopup title]];
	
	if( index > -1 )
	{	
		aMatrix = [[secondaryModePopup target] itemList];
		
	 	if( ![aMatrix selectedCell] )
			[aMatrix selectCell:[[aMatrix cellList] objectAt:index]];
	}

	// NOTE: We have to set the browser delegate by code because otherwise
	// we would be requested to fill the browser before the right cell
	// prototype is set (just right after loading the NIB!) !!!.
	
	cellPrototype = [[CEBrowserCell alloc] init];

	[cellPrototype addFixedTab:4];
	[cellPrototype addFixedTab:20];
	[cellPrototype addFixedTab:-15];

	[browser setMatrixClass:[CECellMatrix class]];
	[browser setCellPrototype:cellPrototype];
	[browser setTarget:self];
	[browser setAction:@selector(selectNewMethod:)];
	[browser setDoubleAction:@selector(selectNewMethod:)];
	[browser setDelegate:self];
	[browser loadColumnZero];
	
	[secondaryBrowser setMatrixClass:[CECellMatrix class]];
	[secondaryBrowser setCellPrototype:cellPrototype];
	[secondaryBrowser setDelegate:self];
	[secondaryBrowser loadColumnZero];
	
	[window setTitleAsFilename:[self filename]];
	[window setMiniwindowImage:[NXImage findImageNamed:"CESmallObjectPink"]];
	
	// Well this is ugly.
	// We should update all the view bevor it gets visible....but then we 
	// have no selected cell and bang..crash..not method..and bang..not string
	// for the Text object. And the find methdo crashes...
	// [browser selectCell:[browser getLoadedCellAtRow:0 inColumn:0]];
	// somehow this doesn't work...but I don't want to waste my time.
	// Ok I fixed the selectMethod to not crash...still its not clean.
	
	[self selectNewMethod:self];

	// ok now we are a new document so lets become active.

	[self _setIsUpdatingTextViews:NO];
	_changedTexts = NO;
	[window makeKeyAndOrderFront:self];
	
	return self;
}

- free
 {
 	// We do not free all the NIB objects...hmm should check that out.
	
  	[window free];
  	[cheatWindow free];
  	[filename free];
	[_docuPath free];
	[_sourcePath free];
	[_headerPath free];
	[[methodList freeObjects] free];
	
  	return [super free];
 }
 
- window
{
	// We have to return the main window to ensure that the right window gets
	// topped when the app decided that we should become visible.
	
	return window;
}

/*
 * Here come the methods we will implement to satisfy our window.
 * They are useful to change the inspector and handle oher tasks.
 */

- reparseMethods:sender
{
	// This is used to keep the two view up to date. Making changes to the
	// We will reset those origin pointers because copying our current text-
	// sections back would most propably destroy something!

	_docuOriginStart = 0;
	_docuOriginEnd = 0;
	_sourceOriginStart = 0;
	_sourceOriginEnd = 0;
	_changedTexts = NO;

	[self _setIsUpdatingTextViews:YES];

	[methodList freeObjects];
	[self _parseMethodFile];
	if( [methodList count] == 0 )
	{
		// Ooops its time to exit..ther are no method.
		// BUG: This is not a Nice way of exiting !
		
		NXRunAlertPanel( NULL, "Could not find a single method inside %s\n"\
							"Methods MUST be definined in the header file "\
							"in order to work with a class!", 
							"Cancel", NULL, NULL, 
							[_headerPath stringValue] );
		
		[[NXApp delegate] addToReleasePool:self];
		return nil;
	}
	// [self checkDocumentation:self];

	[browser loadColumnZero];
	[self _setIsUpdatingTextViews:NO];
	
	return self;
}

- save:sender
{
	// Here we will save all the files in a stupid fashion...anyway.
	// It works for the moment.
	// We will only save file we were able to load !!
	
	id	topWindow;
	
	if( ![window isDocEdited] ) return self;
	
	// Now lets be sure that ALL changes are in the main files...and
	// Nobody sees the changes going on.
	// But this should ONLY happen when the key window is or class browser.
	// We might corrupt data otherwise !!! The selection might not refer to the
	// right positions anymore !
	// We have to take care of which window was the source of the last
	// cahnges. Depening on that we will hvae to make some consitency 
	// adjustment work !
	
	if( _changedTexts )
			topWindow = _changedWindow;
	else	topWindow = nil;

	if( topWindow == window )
		[self _silentlySyncWindows];
		
	// Now if the class has been edited we will save the ASCII Header,
	// RTF source and RTF Documentaion. Quite stupid...but it works for me.
	
	[self _writeText:headerFile withStyle:headerStyle to:_headerPath];
	[self _writeText:sourceFile withStyle:sourceStyle to:_sourcePath];
	[self _writeText:docuFile withStyle:docuStyle to:_docuPath];
	
	// Ok ... well now there is nothing new here.
	
	[cheatWindow setDocEdited:NO];
	[window setDocEdited:NO];

	// Now tell the workspace that we did some changes..

	[[Application workspace] fileSystemChanged];

	// If we have saved from inside the cheat window we have to update the
	// main browser. Otherwise this one will no recognize the changes made.
	
	if( topWindow == cheatWindow ) 
	{
		[self reparseMethods:self];
		[self selectNewMethod:self];
	}

	return self;
}

- close:sender
{
	// Tell both windows to go away...
	// To give feedback on which windows will dissappear we will make the 
	// key first.
	// The browser window is the main window...the only one to focus at...
	// so we can close the other window if the main window is gone...
	
	[window makeKeyAndOrderFront:self];
	[window performClose:self];
	if( [window isVisible] ) return nil;
	
	// Oh...we really did close the window...the cheat window has
	// passed away during the main-close too...so we are done.

	return self;
}

- undo:sender
{
	// Well sorry this is a stupid undo...it only undos alle the changes done
	// since switched to a certain method.
	// This only works if the key window is our main browser.
	// The replacement of the selection will be visiible to give visual
	// feedback that something is happening.
	
	id	ourMethod;
	id	focusView;
	
	if( [NXApp keyWindow] == window )
	{
		[self _setIsUpdatingTextViews:YES];
		ourMethod = selection;
		if( ourMethod != nil )
		{
			[self _showDocuForMethod:ourMethod];
			[self _showSourceForMethod:ourMethod];
		}
		if( [sourceTextView isEditable] )
				focusView = sourceTextView;
		else	focusView = docuTextView;
	
		[focusView setSel:0 :0];
		[focusView scrollSelToVisible];
		_changedTexts = NO;
		[self _setIsUpdatingTextViews:NO];
		[window display];
		[cheatWindow display];
	}
	return self;	
}

- resolveLock:sender
{
	return [[NXApp delegate] showAccessHelper:self];
}

- switchToNewDisplayMode:sender
{
	id	primaryPopMatrix;
	
	// Called by the PopUp when something changes...just ask the browser..
	// who ever that may be..to load its first column.

	primaryPopMatrix = [[modePopup target] itemList];

	if( sender == primaryPopMatrix )
	{
		[browser loadColumnZero];
		[self _checkSelectionInBrowser:browser];
	}	
	else [secondaryBrowser loadColumnZero];
	
	// Perhaps we don't only have the update the browser..but also
	// show the other window. this will disappear in a future release.
	
	if( sender == primaryPopMatrix &&
		[[primaryPopMatrix selectedCell] tag] == CE_BrowsePlainC ) 
		[self showCheatWindow:self];

	return self;
}

- selectNewMethodFromText:sender
{
	const char * realMethod;
	
	// if we can't find the method..lets assume it is a new one...
	// either rename the current...or create a new one.
	 
	NXRunAlertPanel( NULL, "%s\nThis acction should either rename the "\
							"current method or add a new one\n"\
							"Not yet implemented :-(", 
							   "Create New", "Rename Current", "Cancel", 
							   [sender stringValue] );
							   
	// lets reset the right method string..
	
	realMethod = [selection name];
	
	if( realMethod ) [sender setStringValue:realMethod];
	return self;
}

- selectNewMethod:sender
{
	id	ourMethod;
	id	focusView;
	
	// Now lets stop redraws until we have finished...looks a little better.
	// This part is a little trick because somehow I have to find the right
	// selections inside the original texts. Now the Text object is not very
	// helpful here. The find method is more then stupid.
	
	[self _setIsUpdatingTextViews:YES];
	[self _syncWindows];

	// During the init phase ther is no selected method inside the browser..
	// To solve that we simple take the first method we have..
	
	ourMethod = [[browser selectedCell] source];
	if( ourMethod == nil ) ourMethod = [methodList objectAt:0];
	selection = ourMethod;
	
	[selectorNameField setStringValue:[ourMethod selectorName]];
	[methodNameField setStringValue:[ourMethod name]];
	
	// Lets update the method docs/source view with the new text.
	
	[self _showDocuForMethod:ourMethod];
	[self _showSourceForMethod:ourMethod];
	
	// It is time to redraw all the fun stuff again. We will make a startup
	// selection first.

	if( [sourceTextView isEditable] )
			focusView = sourceTextView;
	else	focusView = docuTextView;
	
	[focusView setSel:0 :0];
	[focusView scrollSelToVisible];

	// Now be sure that the changes really get displayed.

	[self _setIsUpdatingTextViews:NO];
	[self _checkSelectionInBrowser:browser];

	[window display];
	[cheatWindow display];

	// Ok here comes the test section
	return self;
}

- selectTrueLine:(int)line inSource:(BOOL)aFlag
{
	id	targetText;
	
	if( aFlag )
	{
		targetText = sourceFile;
		[self showImplementationOnly:self];
	}
	else
	{
		targetText = headerFile;
		[self showInterfaceOnly:self];
	}
	[self showCheatWindow:self];
	
	[targetText selectTrueLine:line];
	[targetText scrollSelToVisible];
	
	return self;
}

- _setIsUpdatingTextViews:(BOOL)flag
{
	// If we have a switch of the docu update stuff...we should take
	// care that the pasteboard won't get corrupted !
	// Which means..wehn finshed with the updates...we will put back the 
	// pervious pastebord contents.
	
	if( _updatingTextViews == NO &&
		flag == YES )
	{
		[window disableDisplay];
		[cheatWindow disableDisplay];
	}

	else if( _updatingTextViews == YES &&
			flag == NO )
	{
		[window reenableDisplay];
		[cheatWindow reenableDisplay];
	}

	_updatingTextViews = flag;

	return self;
}

- _showSourceForMethod:aMethod
{
	// Now lets update the source view. 
	// We assume that no changes get lost...so be sure to backup changes first.
	// Everything that is inside the sourceTextView will get lost.
	
	// Now if we really find the method we can show it.
	
	[self _selectSourceForMethod:aMethod];
	
	// Well...now we have a possible selection inside the original text.
	// If it is no useful selection we don't have any text...and we just mark 
	// the area as useless.
	
	if( _sourceOriginStart > 0 && _sourceOriginEnd > 0 )
	{
		[sourceFile setSel:_sourceOriginStart :_sourceOriginEnd];
		[sourceFile copyTo:_tempPb];
		[sourceTextView setEditable:YES];
		[sourceTextView selectAll:self];
		[sourceTextView pasteFrom:_tempPb];
		
		[sourceTextView setMonoFont:NO];

		if( sourceStyle == CE_FILETYPE_ASCII ) [sourceTextView format:self];
	}
	else
	{
		[sourceTextView setEditable:YES];
		[sourceTextView selectAll:self];
		[sourceTextView delete:self];
		[sourceTextView setEditable:NO];
	}
	// We shouldn't corrupt the fonts if we can only handle ASCII !

	// if( sourceStyle == CE_FILETYPE_ASCII )
	// 		[sourceTextView setMonoFont:YES];
	// else	[sourceTextView setMonoFont:NO];
		
	return self;
}

- _silentlySyncWindows
{
	// this method silently syncs both window. You won't see any selections
	// cluttering your screen.
	// We have to reselect the source and documenation here because otherwise
	// our references won't be corret anymore !!!
	
	[window disableDisplay];
	[cheatWindow disableDisplay];
	[self _setIsUpdatingTextViews:YES];
	[self _syncWindows];
	[self _selectDocuForMethod:selection];
	[self _selectSourceForMethod:selection];
	[self _setIsUpdatingTextViews:NO];
	[cheatWindow reenableDisplay];
	[window reenableDisplay];
		
	return self;
}

- _syncWindows
{
	// This method does sync the cheat window with the main window.
	// If there is a currently used documentation we have to copy it back
	// first !
	// After that it remarks that there are no more differneces between
	// the two views.
	// After all we DON'T trace the changes here. This is just a common
	// source part of _sliently Syncing and selecting a new method !!
	
	if( _docuOriginStart > 0 && _docuOriginEnd > 0 && _changedTexts )
	{
		[docuTextView selectAll:self];
		[docuTextView copyTo:_tempPb];
		[docuFile setSel:_docuOriginStart :_docuOriginEnd];
		[docuFile pasteFrom:_tempPb];
		[docuTextView setSel:0 :0];
	}
	
	// Same for the source code.
	
	if( _sourceOriginStart > 0 && _sourceOriginEnd > 0 && _changedTexts )
	{
		[sourceTextView selectAll:self];
		[sourceTextView copyTo:_tempPb];
		[sourceFile setSel:_sourceOriginStart :_sourceOriginEnd];
		[sourceFile pasteFrom:_tempPb];
		[sourceTextView setSel:0 :0];
	}
	
	_changedTexts = NO;

	return self;
}

- _showDocuForMethod:aMethod
{
	// Now lets update the documentation view. 
	// We assume that no changes get lost...so be sure to backup changes first.
	// Everything that is inside the docuTextView will get lost.
	
	// Now if we really find the method we have to remove the blakn lines and
	// set the right selection part.
	
	[self _selectDocuForMethod:aMethod];
	
	// Well...now we have a possible selection inside the original text.
	// If it is no useful selection we don't have any text...and we just mark 
	// the area as useless.
	
	if( _docuOriginStart > 0 && _docuOriginEnd > 0 )
	{
		[docuFile setSel:_docuOriginStart :_docuOriginEnd];
		[docuFile copyTo:_tempPb];
		[docuTextView setEditable:YES];
		[docuTextView selectAll:self];
		[docuTextView pasteFrom:_tempPb];
	}
	else
	{
		[docuTextView setEditable:YES];
		[docuTextView selectAll:self];
		[docuTextView delete:self];
		[docuTextView setEditable:NO];
	}
	return self;	
}

- showCheatWindow:sender
{
	id	aString;
	// Lets choose a defaults selection to clean up the window.
	[docuFile setSel:0 :0];
	aString = [filename fileName];
	[aString cat:"...Cheat Window"];
	[cheatWindow setMiniwindowImage:
					[NXImage findImageNamed:"ObjectPink.rtf.m.h"]];
	[cheatWindow setTitle:[aString stringValue]];
	[aString free];
	
	[cheatWindow makeKeyAndOrderFront:self];
	return self;
}

- showAllFiles:sender
{
	NXRect	bounds;
	id		aView;

	[cheatWindow disableDisplay];
	
	// Resize the header view to some useful none-zero value.
	// This is necessary to see anything at all.
	// We take the header because it will then end up as the smallest view.
	
	aView = [[headerFile superview] superview];
	[aView getBounds:&bounds];
	[aView sizeTo:bounds.size.width :20];

	// Make the sourceFile and docuFile small enough..and redraw the window.
	
	aView = [[sourceFile superview] superview];
	[aView getBounds:&bounds];
	[aView sizeTo:bounds.size.width :50];
	aView = [[docuFile superview] superview];
	[aView getBounds:&bounds];
	[aView sizeTo:bounds.size.width :50];

	[cheatWindow disableDisplay];
	[cheatWindow display];
	return self;
}

- showDocumentationOnly:sender
{
	[self _resizeCheatWindowFor:docuFile
						andHide:headerFile :sourceFile];

	return self;
}

- showInterfaceOnly:sender
{
	[self _resizeCheatWindowFor:headerFile
						andHide:docuFile :sourceFile];

	return self;
}

- showImplementationOnly:sender
{
	[self _resizeCheatWindowFor:sourceFile
						andHide:docuFile :headerFile];

	return self;
}

- _resizeCheatWindowFor:mainView andHide:secondView :thirdView
{
	NXRect	bounds;
	id		aView;

	[cheatWindow disableDisplay];
	
	// Resize the main view to some useful none-zero value.
	// This is necessary to see anything at all.
	
	aView = [[mainView superview] superview];
	[aView getBounds:&bounds];
	[aView sizeTo:bounds.size.width :20];

	// Make the secondView and thirdView small enough..and redraw the window.
	
	aView = [[secondView superview] superview];
	[aView getBounds:&bounds];
	[aView sizeTo:bounds.size.width :0];
	aView = [[thirdView superview] superview];
	[aView getBounds:&bounds];
	[aView sizeTo:bounds.size.width :0];

	[cheatWindow disableDisplay];
	[cheatWindow display];
	return self;
}

- (int)browser:sender fillMatrix:matrix inColumn:(int)column
{
	int	count;
	
	// BUG: Another "bug" circumvention ??
	// I took this from the RZToDoList app. Perhaps it is quite useless...
	// I don't have the time to diddle with it..but it seams to work without
	// the next line too.
	
	[matrix renewRows:0 cols:1];

	// We don't use lazy browsers because it wouldn't save any space 
	// (maintaining the lists twice) and it is easiers this way.
	
	if( sender == browser )
	{
		switch( [[[[modePopup target] itemList] selectedCell] tag] )
		{
			case CE_BrowseClassInfo:
				count = [self fillClassInfoIntoMatrix:matrix];
				break;
			
			case CE_BrowseInstanceVars:
				count = [self fillInstanceVarsIntoMatrix:matrix];
				break;
			
			case CE_BrowseMethods:
				count = [self fillMethodsIntoMatrix:matrix];
				break;
			
			case CE_BrowseInstanceMethods:
				count = [self fillInstanceMethodsIntoMatrix:matrix];
				break;
			
			case CE_BrowseClassMethods:
				count = [self fillClassMethodsIntoMatrix:matrix];
				break;
			
			case CE_BrowseMethodsByCategory:
				count = [self fillMethodsByCategoryIntoMatrix:matrix];
				break;
			
			case CE_BrowseDependencies:
				count = [self fillDependenciesIntoMatrix:matrix];
				break;
			
			case CE_BrowsePlainC:
			default:
				count = [self fillPlainCIntoMatrix:matrix];
		}
	}
	// Otherwise we have to fill the socond browser..

	else
	{
/*	
		switch( [[[[secondaryModePopup target] itemList] selectedCell] tag] )
		{
			case CE_BrowseReferences:
				count = [self fillReferencesIntoMatrix:matrix
								accordingTo:selection];
				break;
			
			case CE_BrowseHistory:
				count = [self fillHistoryIntoMatrix:matrix 
								accordingTo:selection];
				break;
			
			case CE_BrowseBugs:
			default:
				count = [self fillBugsIntoMatrix:matrix 
								accordingTo:selection];
		}
*/	
		count = 0;
	}
	
	return count;
}

- _checkSelectionInBrowser:aBrowser
{
	id	matrix;
	int	i;
	int	count;
	int	dummy;
	
	// Now lets see if we can make some selections. If possible the browser
	// should reflect the current selection.
		
	if( aBrowser == browser &&
		selection != nil &&
		selection != [[aBrowser selectedCell] source] )
	{
		matrix = [aBrowser matrixInColumn:0];
		[matrix getNumRows:&count numCols:&dummy];
		
		for( i=0; i<count; i++ )
		{
			if( [[matrix cellAt:i :0] source] == selection )
			{
				[matrix selectCellAt:i :0];
				[matrix scrollCellToVisible:i :0];
				break;
			}
		}
	}
	return self;
}

- (int)fillClassInfoIntoMatrix:matrix
{
	int	count;
	id	theCell;
	id	className;
	
	count = 0;

	className = [filename fileBasename];
	[matrix addRow];
	theCell = [matrix cellAt:count :0];
	[theCell setImageNamed:"ClassS" at:0];
	[theCell setText:[className stringValue] at:1 font:FONT_BOLD];
	[theCell setLeaf:YES];
	[theCell setLoaded:YES];
	[theCell setEnabled:NO];

	[className free];
	count++;
	
	[matrix addRow];
	theCell = [matrix cellAt:count :0];
	[theCell setImageNamed:"ProtocolS" at:0];
	[theCell setText:"<SomeProtocol>" at:1];
	[theCell setLeaf:YES];
	[theCell setLoaded:YES];
	[theCell setEnabled:NO];

	count++;
	
	[matrix addRow];
	theCell = [matrix cellAt:count :0];
	[theCell setImageNamed:"CategoryS" at:0];
	[theCell setText:"Archiving" at:1 font:FONT_BOLD];
	[theCell setLeaf:YES];
	[theCell setLoaded:YES];
	[theCell setEnabled:NO];

	count++;
	
	[matrix addRow];
	theCell = [matrix cellAt:count :0];
	[theCell setImageNamed:"CategoryS" at:0];
	[theCell setText:"Working" at:1 font:FONT_BOLD];
	[theCell setLeaf:YES];
	[theCell setLoaded:YES];
	[theCell setEnabled:NO];

	count++;
	
	[matrix addRow];
	theCell = [matrix cellAt:count :0];
	[theCell setImageNamed:"CategoryS" at:0];
	[theCell setText:"Private" at:1 font:FONT_BOLD];
	[theCell setLeaf:YES];
	[theCell setLoaded:YES];
	[theCell setEnabled:NO];

	count++;
	
	return count;
}

- (int)fillInstanceVarsIntoMatrix:matrix
{
	int	count;
	id	theCell;
	
	count = 0;

	// BUG:  There is no way to set the Tabs afterwards !!!!
	
	[matrix addRow];
	theCell = [matrix cellAt:count :0];
	[theCell clearTabs];
	[theCell addFixedTab:4];
	[theCell addProportionalTab:0.3 min:50 max:600];
	[theCell setText:"id" at:0];
	[theCell setText:"anInstance" at:1];
	[theCell setLeaf:YES];
	[theCell setLoaded:YES];
	[theCell setEnabled:NO];

	count++;
	
	[matrix addRow];
	theCell = [matrix cellAt:count :0];
	[theCell clearTabs];
	[theCell addFixedTab:4];
	[theCell addProportionalTab:0.3 min:50 max:600];
	[theCell setText:"id" at:0];
	[theCell setText:"secondInstance" at:1];
	[theCell setLeaf:YES];
	[theCell setLoaded:YES];
	[theCell setEnabled:NO];

	count++;
	
	[matrix addRow];
	theCell = [matrix cellAt:count :0];
	[theCell clearTabs];
	[theCell addFixedTab:4];
	[theCell addProportionalTab:0.3 min:50 max:600];
	[theCell setText:"int *" at:0];
	[theCell setText:"somePointer" at:1];
	[theCell setLeaf:YES];
	[theCell setLoaded:YES];
	[theCell setEnabled:NO];

	count++;
	
	[matrix addRow];
	theCell = [matrix cellAt:count :0];
	[theCell clearTabs];
	[theCell addFixedTab:4];
	[theCell addProportionalTab:0.3 min:50 max:600];
	[theCell setText:"is" at:0];
	[theCell setText:"thisUsefulAnyway" at:1];
	[theCell setLeaf:YES];
	[theCell setLoaded:YES];
	[theCell setEnabled:NO];

	count++;
	
	return count;
}

- (int)fillMethodsIntoMatrix:matrix
{
	int	i;
	int	count;
	id	theMethod;
	id	theCell;
	id	aList;
	
	aList = [MiscSortedList new];
	[aList setSortOrder:Misc_ASCENDING];
	[aList setSortEnabled:YES];

	for( i=0; i<[methodList count];i++ )
		[aList addObject:[methodList objectAt:i]];

	count = 0;

	for( i=0; i<[aList count]; i++ )
	{
		theMethod = [aList objectAt:i];
		
		[matrix addRow];
		
		theCell = [matrix cellAt:count :0];
		[self _fillCell:theCell forMethod:theMethod];
		[theCell setLeaf:YES];
		[theCell setLoaded:YES];

		count++;
	}	
	[aList free];
	return count;
}

- (int)fillInstanceMethodsIntoMatrix:matrix
{
	int	i;
	int	count;
	id	theMethod;
	id	theCell;
	id	aList;
	
	aList = [MiscSortedList new];
	[aList setSortOrder:Misc_ASCENDING];
	[aList setSortEnabled:YES];

	for( i=0; i<[methodList count];i++ )
		[aList addObject:[methodList objectAt:i]];

	count = 0;

	for( i=0; i<[methodList count]; i++ )
	{
		theMethod = [aList objectAt:i];

		if( [theMethod isInstanceMethod] )
		{
			[matrix addRow];
		
			theCell = [matrix cellAt:count :0];
			[self _fillCell:theCell forMethod:theMethod];
			[theCell setLeaf:YES];
			[theCell setLoaded:YES];
	
			count++;
		}
	}	
	[aList free];
	return count;
}

- (int)fillClassMethodsIntoMatrix:matrix
{
	int	i;
	int	count;
	id	theMethod;
	id	theCell;
	id	aList;
	
	aList = [MiscSortedList new];
	[aList setSortOrder:Misc_ASCENDING];
	[aList setSortEnabled:YES];

	for( i=0; i<[methodList count];i++ )
		[aList addObject:[methodList objectAt:i]];


	count = 0;

	for( i=0; i<[methodList count]; i++ )
	{
		theMethod = [aList objectAt:i];

		if( ![theMethod isInstanceMethod] )
		{
			[matrix addRow];
		
			theCell = [matrix cellAt:count :0];
			[self _fillCell:theCell forMethod:theMethod];
			[theCell setLeaf:YES];
			[theCell setLoaded:YES];

			count++;
		}
	}	
	[aList free];
	return count;
}

- (int)fillMethodsByCategoryIntoMatrix:matrix
{
	int	i;
	int	count;
	id	theMethod;
	id	theCell;
	id	className;
	
	count = 0;

	className = [filename fileBasename];
	[matrix addRow];
	theCell = [matrix cellAt:count :0];
	[theCell setImageNamed:"ClassS" at:0];
	[theCell setText:[className stringValue] at:1 font:FONT_BOLD];
	[theCell setLeaf:YES];
	[theCell setLoaded:YES];
	[theCell setEnabled:NO];

	[className free];
	count++;	

	// Now add all the methods...unsorted..
	
	for( i=0; i<[methodList count]; i++ )
	{
		theMethod = [methodList objectAt:i];
		
		[matrix addRow];
		
		theCell = [matrix cellAt:count :0];
		[self _fillCell:theCell forMethod:theMethod];
		[theCell setLeaf:YES];
		[theCell setLoaded:YES];

		count++;
	}	
	return count;
}

- (int)fillDependenciesIntoMatrix:matrix
{
	int	count;
	id	theCell;
	
	count = 0;

	[matrix addRow];
	theCell = [matrix cellAt:count :0];
	[theCell setText:"<appkit/appkit.h>" at:1];
	[theCell setLeaf:YES];
	[theCell setLoaded:YES];
	[theCell setEnabled:NO];

	count++;
	
	[matrix addRow];
	theCell = [matrix cellAt:count :0];
	[theCell setText:"<misckit/misckit.h>" at:1];
	[theCell setLeaf:YES];
	[theCell setLoaded:YES];
	[theCell setEnabled:NO];

	count++;
	
	[matrix addRow];
	theCell = [matrix cellAt:count :0];
	[theCell setText:"\"nothing/works.h\"" at:1];
	[theCell setLeaf:YES];
	[theCell setLoaded:YES];
	[theCell setEnabled:NO];

	count++;
	
	return count;
}

- (int)fillPlainCIntoMatrix:matrix
{
	return [self fillMethodsByCategoryIntoMatrix:matrix];
}

- _fillCell:theCell forMethod:aMethod
{
	if( [aMethod isInstanceMethod] )
			[theCell setImageNamed:"InstanceMethodS" at:0];
	else	[theCell setImageNamed:"ClassMethodS" at:0];
	
	[theCell setText:[aMethod selectorName] at:1];
	[theCell setSource:aMethod];

	// Now set the infos we need for dragging.
	
	[theCell setDraggingSource:aMethod at:0];	
	
	return self;
}

- (int)fillReferencesIntoMatrix:matrix accordingTo:theSelection
{
	return 0;
}

- (int)fillHistoryIntoMatrix:matrix accordingTo:theSelection
{
	return 0;
}

- (int)fillBugsIntoMatrix:matrix accordingTo:theSelection
{
	return 0;
}

- textDidChange:sender
{
	// Here we will make both windows as edited. This is for checking 
	// before saving.
	// But if we are updating the views from the program we won't say that 
	// this are real changes. Only user changes count !!
	// For internal use we are tracking the "real"changes since the last
	// sync between the two windows.
	
	if( sender != docuTextView &&
		sender != sourceTextView &&
		sender != docuFile &&
		sender != headerFile &&
		sender != sourceFile ) return self;
	
	// well only if one of the the relevant texts did send the msg we 
	// will procede.
	
	if( _updatingTextViews == NO &&
		_changedTexts == NO )
	{
		[cheatWindow setDocEdited:YES];
		[window setDocEdited:YES];
		_changedTexts = YES;
		_changedWindow = [sender window];
	}
	
	return self;
}

- textDidGetKeys:sender isEmpty:(BOOL)flag
{
	// This is for tracing down changes inside the window and highlighing the
	// character pairs.
	
	[self textDidChange:sender];

	// Now if someone typed a character that should have a corresponding pair
	// we will highlight temp here.
	
	return self;
}

- _highlightPrevious:(char *)aString inView:aText
{
	// this is for highlighing the character pairs.
	// The current char and his counter part get highlighted if is is found
	// If not we will Ping loud.
	 
	NXSelPt	currentFrom;
	NXSelPt	currentTo;
	NXSelPt	from;
	NXSelPt	to;
	BOOL	gotIt;
	
	[aText getSel:&currentFrom :&currentTo];
	gotIt = [aText findText:aString 
				   ignoreCase:NO backwards:YES wrap:NO];

	if( gotIt )
	{
		[aText getSel:&from :&to];
		[aText setSel:currentFrom.cp-1 :currentFrom.cp];
	//	wait( 1 );
		[aText setSel:from.cp :to.cp];
	}
	else //	NXBeep();
	
	[aText setSel:currentFrom.cp :currentTo.cp];
	return self;
}

- textShouldPerformCompletion:sender
{
	id	nameArray;
	id	aMethod;
	id	aString;
	id	stringToFind;
	int	i;
	int	foundItems;
	int	firstMatch;

	// Ok..only inside the methodField we will do completion

	if( [sender delegate] != methodNameField ) return nil;

	// Now get the string and find what we need.

	stringToFind = [MiscString newWithString:[methodNameField stringValue]];
	nameArray = [MiscStringArray new];

	for( i=0; i<[methodList count]; i++ )
	{
		aMethod = [methodList objectAt:i];
		[nameArray addString:[aMethod selectorName]];
		[nameArray addString:[aMethod name]];
	}
	// Now cont the hits...

	foundItems = 0;
	firstMatch = -1;
	
	for( i=0; i<[nameArray count]; i++ )
	{
		aString = [nameArray objectAt:i];
		if( [aString spotOfString:stringToFind caseSensitive:YES] == 0 )
		{
			foundItems++;
			if( firstMatch == -1 ) firstMatch = i;
		}	
	}
	// Now what have we found.. if there are more then one don't do anything.

	if( foundItems == 1 )
		[methodNameField setStringValue:[nameArray stringAt:firstMatch]];

	// Free it all! We hope that the Array frees the 

	[stringToFind free];
	[nameArray free];
	return self;
}

- windowDidBecomeKey:sender
{
	// Lets look only at the window. Depending on which window has become key
	// We have to make some updates between the two views.

	if( _changedTexts == NO ) return self;

	// If the sender is the window where we did make the last changes there
	// is no need to do some updates at all. Just leave it as it is.
	
	if( sender == _changedWindow ) return self;
	
	if( sender == window ) 
	{
		[self reparseMethods:self];
		[self selectNewMethod:self];
	}

	if( sender == cheatWindow )
		[self _silentlySyncWindows];
	
	return self;
}

- windowWillClose:sender
{
	// Lets save the contents if the user wants it and free ourself.

	int	result;

	if( sender != window ) return self;

	// Oh...looks like somebody wants to close the main window...
	// Lets check if we have something to save.

	if( [window isDocEdited] ) 
	{
		result = NXRunAlertPanel( "Close", "Save changes to %s?", 
								"Save", "Don't Save", "Cancel", 
								[filename stringValue] );

		if( result == NX_ALERTOTHER )
			return nil;

		else if( result == NX_ALERTDEFAULT )
			[self save:self];
	}
	// We will tell the window that they have to interesting information
	// anymore. This is because the app checks for these infos before quitting.
	// And if we decide "Don't save"...this should be done correct.

	[window setDocEdited:NO];
	[cheatWindow setDocEdited:NO];
	[cheatWindow deminiaturize:self];
	[cheatWindow performClose:self];

	// Its time to go....
	
	[[NXApp delegate] addToReleasePool:self];
	return self; 
}

@end

/*
 * History: 13.01.95 Buh
 *			
 *
 * Bugs: - ...
 */